Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 08fd00b3fdae9df733289d2d22487d266dba03f1


Parents : 8133c45
Author : Mark Qvist <bc7291552be7a58f361522990465165c>
Signature : T66BB85Valid, signed by author
Date : 2026-07-19T14:17:32+02:00

Map optimizations. Relaxed loading toggle.

Changes

5 files changed, 77 insertions(+), 89 deletions(-)


Diff

diff --git a/sbapp/mapview/__init__.py b/sbapp/mapview/__init__.py
index cfad7f0f..fc904b28 100644
--- a/sbapp/mapview/__init__.py
+++ b/sbapp/mapview/__init__.py
@@ -1,6 +1,6 @@
from mapview.source import MapSource
from mapview.types import Bbox, Coordinate
-from mapview.view import MapLayer, MapMarker, CustomMapMarker, MapMarkerPopup, MapView, MarkerMapLayer,
+from mapview.view import MapLayer, MapMarker, CustomMapMarker, MapMarkerPopup, MapView, MarkerMapLayer
__all__ = [
"Coordinate",

diff --git a/sbapp/mapview/downloader.py b/sbapp/mapview/downloader.py
index 4869aa1e..a13ae537 100644
--- a/sbapp/mapview/downloader.py
+++ b/sbapp/mapview/downloader.py
@@ -24,12 +24,13 @@ import logging
# user agent is needed because since may 2019 OSM gives me a 429 or 403 server error
# I tried it with a simpler one (just Mozilla/5.0) this also gets rejected
USER_AGENT = 'Kivy-garden.mapview'
+CONCURRENT = 50
import RNS
class Downloader:
_instance = None
- MAX_WORKERS = 40
+ MAX_WORKERS = CONCURRENT
CAP_TIME = 0.01666 # 60 FPS
@staticmethod
@@ -40,11 +41,20 @@ class Downloader:
Downloader._instance = Downloader(cache_dir=cache_dir)
return Downloader._instance
+ def ensure_session(self):
+ if not self.session:
+ adapter = requests.adapters.HTTPAdapter(pool_connections=CONCURRENT, pool_maxsize=CONCURRENT)
+ self.session = requests.Session()
+ self.session.mount('http://', adapter)
+ self.session.mount('https://', adapter)
+ RNS.log(f"Created session {self.session}")
+
def __init__(self, max_workers=None, cap_time=None, **kwargs):
self.cache_dir = kwargs.get('cache_dir', CACHE_DIR)
if max_workers is None: max_workers = Downloader.MAX_WORKERS
if cap_time is None: cap_time = Downloader.CAP_TIME
self.is_paused = False
+ self.session = None
self.cap_time = cap_time
self.executor = ThreadPoolExecutor(max_workers=max_workers)
self._futures = []
@@ -53,6 +63,9 @@ class Downloader:
RNS.log("Creating cache dir "+str(self.cache_dir), RNS.LOG_WARNING)
makedirs(self.cache_dir)
+ requests.packages.urllib3.util.connection.HAS_IPV6 = False
+ self.ensure_session()
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("urllib3.response").setLevel(logging.WARNING)
logging.getLogger("urllib3.connection").setLevel(logging.WARNING)
@@ -70,21 +83,14 @@ class Downloader:
self._futures.append(future)
def download_tile(self, tile):
- # Logger.debug(
- # "Downloader: queue(tile) zoom={} x={} y={}".format(
- # tile.zoom, tile.tile_x, tile.tile_y
- # )
- # )
future = self.executor.submit(self._load_tile, tile)
self._futures.append(future)
def download(self, url, callback, **kwargs):
- # Logger.debug("Downloader: queue(url) {}".format(url))
future = self.executor.submit(self._download_url, url, callback, kwargs)
self._futures.append(future)
def _download_url(self, url, callback, kwargs):
- # Logger.debug("Downloader: download(url) {}".format(url))
response = requests.get(url, **kwargs)
response.raise_for_status()
return callback, (url, response)
@@ -110,8 +116,11 @@ class Downloader:
if tile.map_source.quad_key: uri = tile.map_source.url.format(q=self.__to_quad(tile.tile_x,tile_y,tile.zoom), s=choice(tile.map_source.subdomains))
else: uri = tile.map_source.url.format(z=tile.zoom, x=tile.tile_x, y=tile_y, s=choice(tile.map_source.subdomains))
- response = requests.get(uri, headers={'User-agent': USER_AGENT}, timeout=10)
+ # st = time() # TODO: Remove
+ self.ensure_session()
+ response = self.session.get(uri, headers={'User-agent': USER_AGENT}, timeout=10)
try:
+ # RNS.log(f"Response ready in {RNS.prettyshorttime(time()-st)} for {uri}") # TODO: Remove
response.raise_for_status()
data = response.content
with open(cache_fn, "wb") as fd: fd.write(data)

diff --git a/sbapp/mapview/view.py b/sbapp/mapview/view.py
index a59323e2..f0f5f552 100644
--- a/sbapp/mapview/view.py
+++ b/sbapp/mapview/view.py
@@ -1,5 +1,6 @@
__all__ = ["MapView", "MapMarker", "MapMarkerPopup", "MapLayer", "MarkerMapLayer"]
+import RNS
import webbrowser
from itertools import takewhile
from math import ceil, cos, log2, pi, sin, sqrt
@@ -354,47 +355,17 @@ class MapViewScatter(Scatter):
class MapView(Widget):
- """MapView is the widget that control the map displaying, navigation, and
- layers management.
- """
-
lon = NumericProperty()
- """Longitude at the center of the widget
- """
-
lat = NumericProperty()
- """Latitude at the center of the widget
- """
-
zoom = NumericProperty(0)
- """Zoom of the widget. Must be between :meth:`MapSource.get_min_zoom` and
- :meth:`MapSource.get_max_zoom`. Default to 0.
- """
map_source = ObjectProperty(MapSource())
- """Provider of the map, default to a empty :class:`MapSource`.
- """
-
- double_tap_zoom = BooleanProperty(False)
- """If True, this will activate the double-tap to zoom.
- """
-
- pause_on_action = BooleanProperty(True)
- """Pause any map loading / tiles loading when an action is done.
- This allow better performance on mobile, but can be safely deactivated on
- desktop.
- """
-
- snap_to_zoom = BooleanProperty(True)
- """When the user initiate a zoom, it will snap to the closest zoom for
- better graphics. The map can be blur if the map is scaled between 2 zoom.
- Default to True, even if it doesn't fully working yet.
- """
+ double_tap_zoom = BooleanProperty(False)
+ pause_on_action = BooleanProperty(True)
+ tile_prefetching = BooleanProperty(False)
+ snap_to_zoom = BooleanProperty(True)
animation_duration = NumericProperty(100)
- """Duration to animate Tiles alpha from 0 to 1 when it's ready to show.
- Default to 100 as 100ms. Use 0 to deactivate.
- """
delta_x = NumericProperty(0)
delta_y = NumericProperty(0)
@@ -410,8 +381,6 @@ class MapView(Widget):
__events__ = ["on_map_relocated"]
- # Public API
-
@property
def viewport_pos(self):
vx, vy = self._scatter.to_local(self.x, self.y)
@@ -689,9 +658,7 @@ class MapView(Widget):
self.delta_x = scatter.x + self.delta_x * f
self.delta_y = scatter.y + self.delta_y * f
# back to 0 every time
- scatter.apply_transform(
- Matrix().translate(-scatter.x, -scatter.y, 0), post_multiply=True
- )
+ scatter.apply_transform(Matrix().translate(-scatter.x, -scatter.y, 0), post_multiply=True)
# avoid triggering zoom changes.
self._zoom = zoom
@@ -916,19 +883,15 @@ class MapView(Widget):
# print(f"Self = {self._scale} Scale = {scale} Rescale = {rescale} Zoom = {self.zoom}")
def on_touch_down(self, touch):
- if not self.collide_point(*touch.pos):
- return
- if self.pause_on_action:
- self._pause = True
+ if not self.collide_point(*touch.pos): return
+ if self.pause_on_action: self._pause = True
# if "button" in touch.profile:
# print(f"Scale = {self._scale} Scatter = {self._scatter.scale}")
if "button" in touch.profile and touch.button in ("scrolldown", "scrollup"):
self._allow_snap = False
- if self.snap_to_zoom:
- d = 1 if touch.button == "scrolldown" else -1
- else:
- d = 0.1 if touch.button == "scrolldown" else -0.1
+ if self.snap_to_zoom: d = 1.0 if touch.button == "scrolldown" else -1.0
+ else: d = 0.1 if touch.button == "scrolldown" else -0.1
self.animated_diff_scale_at(d, *touch.pos)
return True
@@ -945,8 +908,7 @@ class MapView(Widget):
return True
touch.grab(self)
self._touch_count += 1
- if self._touch_count == 1:
- self._touch_zoom = (self.zoom, self._scale)
+ if self._touch_count == 1: self._touch_zoom = (self.zoom, self._scale)
return super().on_touch_down(touch)
def on_touch_up(self, touch):
@@ -1196,8 +1158,7 @@ class MapView(Widget):
turn += 1
def load_tile(self, x, y, size, zoom):
- if self.tile_in_tile_map(x, y) or zoom != self._zoom:
- return
+ if self.tile_in_tile_map(x, y) or zoom != self._zoom: return
self.load_tile_for_source(self.map_source, 1.0, size, x, y, zoom)
# XXX do overlay support
self.tile_map_set(x, y, True)
@@ -1211,8 +1172,7 @@ class MapView(Widget):
tile.pos = (x * size + self.delta_x, y * size + self.delta_y)
tile.map_source = map_source
tile.state = "loading"
- if not self._pause:
- map_source.fill_tile(tile)
+ if not self._pause: map_source.fill_tile(tile)
self.canvas_map.add(tile.g_color)
self.canvas_map.add(tile)
self._tiles.append(tile)
@@ -1240,9 +1200,6 @@ class MapView(Widget):
canvas_map.before.clear()
self._tilemap = {}
- # unsure if it's really needed, i personnally didn't get issues right now
- # btiles.sort(key=lambda z: -z.zoom)
-
# add all the btiles into the back canvas.
# except for the tiles that are owned by the current zoom level
for tile in btiles[:]:
@@ -1261,26 +1218,22 @@ class MapView(Widget):
# clear the map of all tiles.
self.canvas_map.clear()
self.canvas_map.before.clear()
- for tile in self._tiles:
- tile.state = "done"
+ for tile in self._tiles: tile.state = "done"
del self._tiles[:]
del self._tiles_bg[:]
self._tilemap = {}
def tile_map_set(self, tile_x, tile_y, value):
key = tile_y * self.map_source.get_col_count(self._zoom) + tile_x
- if value:
- self._tilemap[key] = value
- else:
- self._tilemap.pop(key, None)
+ if value: self._tilemap[key] = value
+ else: self._tilemap.pop(key, None)
def tile_in_tile_map(self, tile_x, tile_y):
key = tile_y * self.map_source.get_col_count(self._zoom) + tile_x
return key in self._tilemap
def on_size(self, instance, size):
- for layer in self._layers:
- layer.size = size
+ for layer in self._layers: layer.size = size
self.center_on(self.lat, self.lon)
self.trigger_update(True)
@@ -1289,23 +1242,13 @@ class MapView(Widget):
self.trigger_update(True)
def on_map_source(self, instance, source):
- if isinstance(source, string_types):
- self.map_source = MapSource.from_provider(source)
+ if isinstance(source, string_types): self.map_source = MapSource.from_provider(source)
elif isinstance(source, (tuple, list)):
cache_key, min_zoom, max_zoom, url, attribution, options = source
- self.map_source = MapSource(
- url=url,
- cache_key=cache_key,
- min_zoom=min_zoom,
- max_zoom=max_zoom,
- attribution=attribution,
- cache_dir=self.cache_dir,
- **options
- )
- elif isinstance(source, MapSource):
- self.map_source = source
- else:
- raise Exception("Invalid map source provider")
+ self.map_source = MapSource(url=url, cache_key=cache_key, min_zoom=min_zoom, max_zoom=max_zoom,
+ attribution=attribution, cache_dir=self.cache_dir, **options)
+ elif isinstance(source, MapSource): self.map_source = source
+ else: raise Exception("Invalid map source provider")
self.zoom = clamp(self.zoom, self.map_source.min_zoom, self.map_source.max_zoom)
self.remove_all_tiles()
self.trigger_update(True)

diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py
index de79b9a1..f45d3485 100644
--- a/sbapp/sideband/core.py
+++ b/sbapp/sideband/core.py
@@ -809,6 +809,7 @@ class SidebandCore():
if not "map_cluster" in self.config: self.config["map_cluster"] = True
if not "map_interfaces" in self.config: self.config["map_interfaces"] = True
if not "map_connection_maps" in self.config: self.config["map_connection_maps"] = False
+ if not "map_relaxed_loading" in self.config: self.config["map_relaxed_loading"] = False
if not "discover_interfaces" in self.config: self.config["discover_interfaces"] = True
if not "map_storage_path" in self.config: self.config["map_storage_path"] = None

diff --git a/sbapp/ui/map.py b/sbapp/ui/map.py
index 0ca2f173..9adb5267 100644
--- a/sbapp/ui/map.py
+++ b/sbapp/ui/map.py
@@ -71,6 +71,13 @@ class Map():
mapview = MapView(map_source=msource, zoom=mzoom, lat=mlat, lon=mlon)
mapview.snap_to_zoom = False
mapview.double_tap_zoom = True
+ if self.app.sideband.config["map_relaxed_loading"] == True:
+ mapview.pause_on_action = True
+ mapview.tile_prefetching = False
+ else:
+ mapview.pause_on_action = False
+ mapview.tile_prefetching = True
+
self.map = mapview
self.screen.ids.map_layout.map = mapview
self.screen.ids.map_layout.add_widget(self.screen.ids.map_layout.map)
@@ -351,6 +358,7 @@ class Map():
self.map_settings_screen.ids.map_cluster.active = self.app.sideband.config["map_cluster"]
self.map_settings_screen.ids.map_interfaces.active = self.app.sideband.config["map_interfaces"]
self.map_settings_screen.ids.map_connection_maps.active = self.app.sideband.config["map_connection_maps"]
+ self.map_settings_screen.ids.map_relaxed_loading.active = self.app.sideband.config["map_relaxed_loading"]
def settings_init(self):
self.settings_load_states()
@@ -361,8 +369,19 @@ class Map():
self.app.sideband.config["map_cluster"] = self.map_settings_screen.ids.map_cluster.active
self.app.sideband.config["map_interfaces"] = self.map_settings_screen.ids.map_interfaces.active
self.app.sideband.config["map_connection_maps"] = self.map_settings_screen.ids.map_connection_maps.active
+ self.app.sideband.config["map_relaxed_loading"] = self.map_settings_screen.ids.map_relaxed_loading.active
self.app.sideband.save_configuration()
+ def relaxed_loading_toggle(sender=None, event=None):
+ if self.map:
+ if self.map_settings_screen.ids.map_relaxed_loading.active:
+ self.map.tile_prefetching = False
+ self.map.pause_on_action = True
+ else:
+ self.map.tile_prefetching = True
+ self.map.pause_on_action = False
+ map_settings_save()
+
def external_toggle(sender=None, event=None):
self.app.sideband.config["map_storage_path"] = None
map_settings_save()
@@ -384,6 +403,7 @@ class Map():
self.map_settings_screen.ids.map_cluster.bind(active=map_settings_save)
self.map_settings_screen.ids.map_interfaces.bind(active=map_settings_save)
self.map_settings_screen.ids.map_connection_maps.bind(active=map_settings_save)
+ self.map_settings_screen.ids.map_relaxed_loading.bind(active=relaxed_loading_toggle)
def settings_action(self, sender=None, direction="left"):
if not self.app.root.ids.screen_manager.has_screen("map_settings_screen"):
@@ -914,6 +934,21 @@ MDScreen:
pos_hint: {"center_y": 0.3}
active: False
+ MDBoxLayout:
+ orientation: "horizontal"
+ padding: [0,0,dp(24),0]
+ size_hint_y: None
+ height: dp(48)
+
+ MDLabel:
+ text: "Relaxed Loading"
+ font_style: "H6"
+
+ MDSwitch:
+ id: map_relaxed_loading
+ pos_hint: {"center_y": 0.3}
+ active: False
+
MDBoxLayout:
orientation: "horizontal"
padding: [0,0,dp(24),0]


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────